package.cpath  = "./?.so;" .. package.cpath
local filename = "agent.lua"

local skynet = require "skynet"
local netpack = require "skynet.websocketnetpack"
local cluster = require "skynet.cluster"
local tonumber = tonumber

local json = require "cjson"
json.encode_sparse_array(true,1,1)

local filelog = require "filelog"
local configdao = require "configdao"
local timetool = require "timetool"
local redisdao = require "redisdao"
local md5 = require "md5"
local base = require "base"
local tabletool = require "tabletool"

local platform2login_key = ""

local Agent = {
	fd = nil,
	socket = nil,
	eventpool = {},
    username = "",
    cpass = "",
	heart_interval_time = 0,
    last_heart_time = 0,
	loginsvr_conf = {},
    gatesvr_conf = {},
    gamesvr_conf = {},
    state = 0,          --状态 0未登录 1已登录 2游戏中
    target_state = 0,   --目标状态，机器人state必须达到目标状态
    target_game = nil,  --目标游戏，机器人要进入的游戏服
    target_table = nil, --目标桌子，机器人要进入的桌子
    target_match = nil, --目标匹配场类型 diamond 或者 coin
    target_level = nil, --目标匹配场级别 1初级 2中级 3高级
    match_out_time = 60,--匹配超时时间
    start_matchtime = 0,--开始匹配时间

    --玩家基本信息
    rid = 0,
	info = nil,
	money = nil,
    gamenode = nil,
    table_index = nil,
    robotinfo = nil,
    robotmoney = nil,
    table_type = nil,
    gameinfo = {            --游戏相关信息
        round_num = 0,      --连续游戏局数
        total_score = 0,    --累积输赢分数
        cur_score = 0,      --本局结算分数
        start_time = 0,     --开始游戏时间
        ready_time = 0,     --游戏准备时间
        robot_num = 0,      --桌内机器人数
        max_player_num = 0, --桌内最大玩家数
    },

    --桌子信息
    tableinfo = nil,
}

-----------------------------------

function Agent:process(session, source, calltype, msgname, ...)
	if msgname == nil then
		filelog.sys_error(filename.." [BASIC_EVENTMNG] invalid msgname")
		return
	end
	--filelog.sys_protomsg("line44__" .. skynet.self().."__"..calltype.."_"..msgname.."_request", ...)
	local f = self.eventpool[msgname]
	if f == nil then
		filelog.sys_error(filename.." [BASIC_EVENTMNG] invalid  msgname:"..msgname, ...)		
		return
	end
	local status, result = skynet.pcall(f.process, f, session, source, ...)
	if not status then
		filelog.sys_error(filename.." [BASIC_EVENTMNG] skynet.pcall failed", msgname, result, ...)
	end
    if calltype ~= "server" then
        skynet.retpack(result)
    end

    return result
end

--一些初始化
function Agent:start()
	skynet.dispatch("lua", function (session, source, ...)
		Agent:process(session, source,"lua", ...)
	end)
	
	debug.getregistry().PROTOBUF_ENV = skynet.call(".pbcservice", "lua", "get_protobuf_env")
	self.protobuf = require "protobuf"
	self.decode = require "protopack"

	self:add_eventbyname("cmd", "agentmsg/agentcmd")
end

--向服务器发送请求
function Agent:send_reqmsg_toserver(fd, msgname, msgbody)
	self.socket.send(fd, self.decode.c2spack(self.protobuf, msgname, msgbody))
end

--添加消息处理模块
function Agent:add_eventbyname(name, event)
	self.eventpool[name] = require(event)
	self.eventpool[name].service = Agent
end

--主要做一些初始化
function Agent:create_session(conf)
    self.rid = conf.robotinfo.rid                           --玩家rid
    self.username = conf.robotinfo.username                 --用户名
    self.cpass = conf.robotinfo.cpass                       --登录密码
    self.robotinfo = tabletool.deepcopy(conf.robotinfo)     --机器人信息
    self.robotmoney = tabletool.deepcopy(conf.robotmoney)   --机器人信息
	self.heart_interval_time = conf.heart_interval_time     --心跳间隔时间
	self.loginsvr_conf = tabletool.deepcopy(conf.loginsvr_conf)
    self.gatesvr_conf = tabletool.deepcopy(conf.gatesvr_conf)
    self.gamesvr_conf = tabletool.deepcopy(conf.gamesvr_conf)
    platform2login_key = conf.platform2login_key or ""
    self.socket = require "client.websocket"

	-- 处理服务器消息
	skynet.fork(self.handler_servermsg, self)

    -- 发送心跳,同时检测agent状态
    skynet.fork(function()
        while true do
            local now = timetool.get_time()
            if self.state >= 1 and self.fd then
                if now - self.last_heart_time >= self.heart_interval_time then
                    self:send_reqmsg_toserver(self.fd, "heart", {})
                    self.last_heart_time = now
                end
            end
            skynet.sleep(self.heart_interval_time * 100)
            if self.state ~= self.target_state then
                now = timetool.get_time()
                if not self.target_match or self.start_matchtime == 0 or ((now - self.start_matchtime) > self.match_out_time) then
                    filelog.sys_error("agent state != target state!", self.rid, self.state, self.target_state, self.target_match, now-self.start_matchtime)
                    self:agentexit(true)
                end
            elseif not self.state or self.state <= 1 then
                filelog.sys_error("agent target state == 1 or nil ,force exit agent", self.rid, self.state)
                self:agentexit(true)
            end
        end
    end)

    --添加逻辑处理文件
    local logicname = "robotlogic/" .. ( conf.logicname  or "base") .. "logic"
    local fp = io.open(logicname .. ".lua")
    if not fp then
        logicname = "robotlogic/baselogic"
    else
        fp:close()
    end
    self:add_eventbyname("response", logicname)

	return true
end

--关闭服务
function Agent:exit_service( )
    skynet.pcall(cluster.send, "gatenode", ".router", "quitgame", self.rid)
    skynet.sleep(200)
	skynet.exit()
end

--结束agent
function Agent:agentexit(is_selfclose, gamenode)
    gamenode = gamenode or self.gamenode

    --已关闭
    if self.state == -1 then
        return
    end

    --在桌内，先离桌
    if self.state == 2 or (self.table_index and self.table_index > 0) then
        self:robot_leavetable(self.gamenode, self.table_index)
        return
    end

    --在匹配中，取消匹配
    if self.fd and self.target_game and self.target_level and self.start_matchtime ~= 0 then
        local gamename = string.match(self.target_game,"(.+)nod%a") or string.sub(self.target_game, 1, -5)
        local msgname = gamename .. ".cancelmatch"
        if self.target_match == "coin" then
            msgname = gamename .. ".cancelcoinmatch"
        end
        self:send_reqmsg_toserver(self.fd, msgname, {match_type = self.target_level})
        skynet.fork(self.force_exit, self)
        return
    end

	if is_selfclose then
		skynet.pcall(skynet.send, ".robotmgr", "lua", "delete_robot", self.rid)
	end

    --更新调度任务  
    skynet.pcall(skynet.send, ".schedulemgr", "lua", "update_schedule_task", self.schedule_id or 0, self.rid)
    self.schedule_id = nil

    --关闭socket
    if self.fd then
        self.socket.close(self.fd)
        self.fd = nil
    end
	self.socket = nil
    self.state = -1

    --身上钱放回库存
    if self.money then
        local money = tabletool.deepcopy(self.money)
        self.money = nil
        for moneytype,value in pairs(money) do
            if value > 0 then
                skynet.pcall(skynet.call, ".inventorymgr", "lua", "save_money", self.rid, moneytype, self.robotmoney[moneytype] or 0, value, gamenode)
            end
        end
    end
    self:exit_service()
end

--生成token
local function gentoken(username)
    local tokentime = os.time()
    local newtoken = md5.sumhexa(username .. tokentime .. platform2login_key ) .. tokentime
    return newtoken
end

--登录
function Agent:login()
    if self.fd then
        self.socket.close(self.fd)
        self.fd = nil
    end

    if self.loginsvr_conf.ip and self.loginsvr_conf.port then
        local ip = self.loginsvr_conf.ip
        local port = self.loginsvr_conf.port
        local isok, fd = pcall(self.socket.connect, ip, port)
        if isok and fd then
            self.fd = fd
	        local newtoken = gentoken(self.username)
            self:send_reqmsg_toserver(fd,"authuser",{username = self.username, cpass = self.cpass, logintoken = newtoken})
        else
            return false
        end
    
        return true
    end
end

--登录游戏
function Agent:entergame(conf)
    if self.fd then
        self.socket.close(self.fd)
        self.fd = nil
    end

    local ip = self.gatesvr_conf.ip
    local port = conf.gateport or self.gatesvr_conf.port
    local isok, fd = pcall(self.socket.connect, ip, port)
    if isok and fd then
        self.fd = fd
        self:send_reqmsg_toserver(fd,"entergame",{rid = conf.rid, uid = 0, gatetoken = conf.gatetoken,expiretime = conf.expiretime, clientver = "0000"})
    else
        return false
    end
    
    return true
end

--进入匹配场
function Agent:entermatchtable()
    if self.target_match == "diamond" and self.target_game and self.target_level then
        self.target_table = nil
        self.start_matchtime = timetool.get_time()
        local gamename = string.match(self.target_game,"(.+)nod%a") or string.sub(self.target_game, 1, -5)
        self:send_reqmsg_toserver(self.fd, gamename .. ".entermatchtable",{match_type = self.target_level})
    end
end

--进入金币匹配场
function Agent:entercoinmatchtable()
    if self.target_match == "coin" and self.target_game and self.target_level then
        self.target_table = nil
        self.start_matchtime = timetool.get_time()
        local gamename = string.match(self.target_game,"(.+)nod%a") or string.sub(self.target_game, 1, -5)
        self:send_reqmsg_toserver(self.fd, gamename .. ".entercoinmatchtable",{match_type = self.target_level})
    end
end

--进入桌子
function Agent:entertable()
    if self.target_game and self.target_table then
        local gamename = string.match(self.target_game,"(.+)nod%a") or string.sub(self.target_game, 1, -5)
        self:send_reqmsg_toserver(self.fd, gamename .. ".entertable",{table_index = self.target_table})
    end
end

--重新进入桌子（用于匹配场）
function Agent:reentertable(table_index, gamename)
    if gamename and table_index then
        self.target_table = table_index
        self:send_reqmsg_toserver(self.fd, gamename .. ".reentertable",{ })
    end 
end

--坐下
function Agent:sitdowntable()
    if self.gamenode and self.table_index then
        local gamename = string.match(self.gamenode,"(.+)nod%a") or string.sub(self.target_game, 1, -5)
        self:send_reqmsg_toserver(self.fd, gamename .. ".sitdowntable",{ })
    end 
end

--准备
function Agent:gameready()
    if self.state == 2 and self.gamenode and self.table_index then
        local gamename = string.match(self.gamenode,"(.+)nod%a") or string.sub(self.target_game, 1, -5) or ""
        self:send_reqmsg_toserver(self.fd, gamename .. ".gameready",{ })
    end 
end

--离开桌子
function Agent:leavetable()
    if self.gamenode and self.table_index then
        local gamename = string.match(self.gamenode,"(.+)nod%a") or string.sub(self.target_game, 1, -5)
        self:send_reqmsg_toserver(self.fd, gamename .. ".leavetable",{ })
    else
        self.state = 1
    end
end


--执行操作
function Agent:doaction(requestmsg)
    if self.state == 2 and self.gamenode then
        local gamename = string.match(self.gamenode,"(.+)nod%a") or string.sub(self.target_game, 1, -5)
        self:send_reqmsg_toserver(self.fd, gamename .. ".doaction", requestmsg)
    end
end

--发送请求
function Agent:send_request(msgname, msgbody)
    if self.state == 2 and self.gamenode then
        local gamename = string.match(self.gamenode,"(.+)nod%a") or string.sub(self.target_game, 1, -5)
        self:send_reqmsg_toserver(self.fd, gamename .. "." .. msgname, msgbody)
    end
end

--接收包
function Agent:recv_package(fd)
    if not fd then return end
	local isok, r, istimeout = pcall(self.socket.recv, fd, 10)
	if not r or not isok then
		return nil
	end
	if r == "" and not istimeout then
		return -1
	end
	return r
end

--处理服务器发来的数据包
function Agent:dispatch_package(fd)
    if not fd then
        return
    end

	while true do
		local data = self:recv_package(fd)
		if not data or data == "" then
			break
        elseif data == -1 then
            return -1
		else
            return true,self.decode.s2cunpack(self.protobuf, data)
        end
	end
end

--处理服务器消息
function Agent:handler_servermsg()
    while true do
        local status,msghead,msgbody = self:dispatch_package(self.fd)
        if status == true then
            skynet.fork(Agent.process, self, 0, 0, "server", "response", msghead, msgbody)
        end
        skynet.sleep(20)
    end
end

--更新用户信息
function Agent:updateinfo(roleinfo, is_init)
    if is_init then
        self.info = {
            rid = roleinfo.rid or 0,
            rolename = roleinfo.rolename or "",
            level = roleinfo.level or 99,
            logo = roleinfo.logo or "1",
            sex = roleinfo or 2,
            intro = roleinfo.intro or "",
            ip_addr = roleinfo.ip_addr,
            gps_addr = roleinfo.gps_addr,
            upvoted_num = roleinfo.upvoted_num,
            downvoted_num = roleinfo.downvoted_num,
            logoframe = roleinfo.logoframe,
        }
        self.rid = self.info.rid

        self.money = {
            coin = roleinfo.coin or 0,
            fangka = roleinfo.fangka or 0,
            diamond = roleinfo.diamond or 0,
            lock_diamond = roleinfo.lock_diamond or 0,
            lock_coin = roleinfo.lock_coin or 0,
        }

        --如果身上有钱，存回库存
        for moneytype, value in pairs(self.money) do
            if value > 0 then
                local result = skynet.call(".inventorymgr", "lua", "save_money", self.rid, moneytype, value, value, self.gamenode)
                if result then
                    self.money[moneytype] = 0
                end
            end
        end
        return
    end
    
    --更新信息
    for k,v in pairs(roleinfo) do
        if self.info[k] ~= v then
            self.info[k] = v
        end
    end
end

--修改用户信息
--主要修改金钱
function Agent:changeinfo()
    --初始化金钱
    if self.robotmoney then
        for moneytype, value in pairs(self.robotmoney) do
            if value > 0 then
                local result = skynet.call(".inventorymgr", "lua", "draw_money", self.rid, moneytype, self.money[moneytype] or 0, value, self.target_game)
                if result then
                    self.money[moneytype] = value
                elseif moneytype == "diamond" then
                    return false
                end
            end
        end
    end
   
   return true
end

--更新用户金钱
function Agent:updatemoney(rolemoney)
    if self.money then
        for k,v in pairs(rolemoney) do
            if k ~= "rid" and self.money[k] and self.money[k] ~= v then
                self.money[k] = tonumber(v)
            end
        end
    end
end

--注册逻辑处理模块
--用于处理response类型消息
function Agent:register_logicmodule(logicname)
    local logicmodule = require(event)
    if logicmodule then
        self.eventpool["response"] = logicmodule
        self.eventpool["response"].service = Agent
    end
end

--机器人进去游戏
function Agent:robot_entergame()
    self.target_state = 1
    if self.state == 0 then
        self:login()
    end
    
    return 1
end

--机器人进入桌子
function Agent:robot_entertable(ganmename, table_index, schedule_id)
    self.target_state = 2
    self.target_game = ganmename
    self.target_table = table_index
    self.schedule_id = schedule_id

    if self.state == 0 then
        self:login()
    elseif self.state == 1 then
        self:entertable()
    elseif self.state == 2 then
        --已在游戏中，不可能出现
        self:leavetable()
        return 0
    end

    return 1
end

--机器人进匹配场
function Agent:robot_entermatch(ganmename, match_type, match_level, schedule_id)
    self.target_state = 2
    self.target_game = ganmename
    self.target_match = match_type
    self.target_level = match_level
    self.schedule_id = schedule_id

    if self.state == 0 then
        self:login()
    elseif self.state == 1 then
        if match_type == "diamond" then
            self:entermatchtable()
        elseif match_type == "coin" then
            self:entercoinmatchtable()
        end
    elseif self.state == 2 then
        --已在游戏中，不可能出现
        self:leavetable()
        return 0
    end

    return 1
end

--机器人离开桌子
function Agent:robot_leavetable(ganmename, table_index, schedule_id)
    if self.gamenode ~= ganmename or self.table_index ~= table_index then
        filelog.sys_error("Agent:robot_leavetable gamename not equal! ",ganmename, table_index, self.gamenode, self.table_index)
    end

    self.target_state = 1
    self.target_game = nil
    self.target_table = nil
    self.target_match = nil
    self.target_level = nil
    self.start_matchtime = 0
    self.schedule_id = schedule_id

    if self.state == 2 then
        self:leavetable()
    else
        self.table_index = nil
    end

    return 1
end

--初始化桌子信息，将从游戏服拉取需要的桌子数据
function Agent:init_tableinfo()
    if self.gamenode and  self.table_index then
        local status,result = skynet.pcall(cluster.call, self.gamenode, ".router", "get_tableinfo", self.table_index, self.rid)
        if status and result then
            self.tableinfo = result or {}
        else
            filelog.sys_error("get tableinfo failed from gamenode! ", self.gamenode, self.table_index, status, result)
        end
    end
end

--获取桌子信息
function Agent:get_tableinfo()
    return self.tableinfo
end

--获得桌子类型（diamond fangka coin)
function Agent:get_tabletype()
    if self.table_type == 1 then
        return "diamond"  
    elseif self.table_type == 2 then
        return "fangka"
    elseif self.table_type == 3 then
        return "coin"
    elseif self.table_type == nil then
        return "diamond"
    end
end

--重置游戏信息
function Agent:reset_gameinfo()
    self.gameinfo = {
        round_num = 0,      --连续游戏局数
        total_score = 0,    --累积输赢分数
        cur_score = 0,      --本局结算分数
        start_time = 0,     --开始游戏时间
        ready_time = 0,     --游戏准备时间
        robot_num = 0,      --桌内机器人数
        max_player_num = 0, --桌内最大玩家数
    }
end

--延时离桌
function Agent:delay_leavetable(ns, gamename, table_index, schedule_id)
    ns = ns or base.get_random(1,4)
    gamename = gamename or self.gamenode
    table_index = table_index or self.table_index
    skynet.sleep( math.floor(ns * 100))
    self:robot_leavetable(gamename, table_index, schedule_id)
end

--检测离桌
function Agent:check_leavetable()
    --公有的离桌逻辑

    --钱不够不再准备
    local robotmoney = self.robotmoney or {}
    local initmoney = robotmoney.diamond or 0
    local curmoney = self.money.diamond or 0
    local leave_diamond = self.gameinfo.leave_diamond or 0
    if self.table_type == 3 then    --金币场
        initmoney = robotmoney.coin or 0
        curmoney = self.money.coin or 0
    end

    --钱不够不会再准备
    if curmoney < leave_diamond then
        return true
    end

    --按身上钱多少，概率离桌
    local p = math.floor(curmoney / initmoney * 100) + 15
    if p < 80 and base.get_random(1,100) >= p then      --输钱
        --延时并离桌
        local ns = base.get_random(1,4)
        skynet.send( skynet.self(), "lua", "cmd", "delay_leavetable", ns)
        return true
    elseif p > 150 and base.get_random(100,300) <= p then --赢钱
        --延时并离桌
        local ns = base.get_random(1,4)
        skynet.send( skynet.self(), "lua", "cmd", "delay_leavetable", ns)
        return true
    end

    --局数超过1局概率离桌，超过5桌必离桌
    local round_num = self.gameinfo.round_num or 0
    if round_num > 1 and base.get_random(1,6) <= round_num then
        --延时并离桌
        local ns = base.get_random(1,4)
        skynet.send( skynet.self(), "lua", "cmd", "delay_leavetable", ns)
        return true
    end

    --调用游戏自己的离桌逻辑
    local gamelogic = self.eventpool["response"]
    return gamelogic:check_leavetable()
end

--强制结束,等待10s
function Agent:force_exit()
    skynet.sleep(1000)
    if self.state == 1 and self.start_matchtime ~= 0 then
        self.target_state = 0
        self.target_game = nil
        self.target_table = nil
        self.target_match = nil
        self.target_level = nil
        self.table_index = nil
        self.start_matchtime = 0
        filelog.sys_error("------force exit robot------",self.rid)
        self:agentexit(true)
    end
end

skynet.start(function()
	Agent:start()	
end)
